package org.jdpf.gui.rcp.neteditor.core.util;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TreeMap;

import org.jdpf.core.kernel.elements.ArcExpression;
import org.jdpf.core.kernel.elements.ITransition;
import org.jdpf.core.kernel.elements.InputArc;
import org.jdpf.core.kernel.elements.Net;
import org.jdpf.core.kernel.elements.OutputArc;
import org.jdpf.core.kernel.elements.Place;
import org.jdpf.core.kernel.elements.Token;
import org.jdpf.core.kernel.exceptions.BundleNotFoundException;
import org.jdpf.core.kernel.exceptions.CoreException;
import org.jdpf.core.kernel.execution.NetValidatorExecutor;
import org.jdpf.core.kernel.execution.Task;
import org.jdpf.core.kernel.logging.ILogger;
import org.jdpf.core.kernel.logging.LoggerFactory;
import org.jdpf.core.kernel.plugins.BundleToolBox;
import org.jdpf.core.kernel.plugins.IPluginsFrameworkConstants;
import org.jdpf.core.netvalidator.service.NetValidationService;
import org.jdpf.core.plugins.blocks.AbstractTransitionPlugin;
import org.jdpf.core.plugins.datatypes.AbstractDataType;
import org.jdpf.core.plugins.parameters.Parameters;
import org.jdpf.gui.rcp.neteditor.core.blocks.*;
import org.jdpf.gui.rcp.neteditor.core.elements.GraphicNet;
import org.jdpf.gui.rcp.neteditor.core.elements.GraphicNetAsTaskValidationStatus;
import org.jdpf.gui.rcp.neteditor.core.elements.GraphicWire;
import org.osgi.framework.Bundle;

public class Utility {

	private static final ILogger logger = LoggerFactory.getDefault()
			.getLogger();

	private static Utility instance = new Utility(); // Singleton

	public static Utility getDefault() {
		return instance;
	}

	private Utility() {
		// no op
	}

	public Net getNetFromGraphicNet(GraphicNet gnet) {
		Net net = null;

		net = new Net();
		net.setName(gnet.getNetName());
		TreeMap<String, AbstractTransitionPlugin> pluginMap = new TreeMap<String, AbstractTransitionPlugin>();

		// Collecting all transition services (AbstractTransitionPlugin)
		for (IGraphicBlock block : gnet.getBlockList()) {
			AbstractTransitionPlugin plugin = BundleToolBox.getDefault()
					.getPlugin(block.getBundleSymbolicName(), block.getTransitionId(),
							block.getTransitionName());
			pluginMap.put(block.getTransitionId(), plugin);

			// Adding the transition to the net
			net.add(plugin);
		}

		// Creating Places and Arcs (except start and end places)
		for (GraphicWire wire : gnet.getGraphicWireList()) {
			String id = wire.getSource().getTransitionId() + "-"
					+ wire.getSourceOrder() + "-"
					+ wire.getTarget().getTransitionId() + "-"
					+ wire.getTargetOrder();

			AbstractTransitionPlugin source = pluginMap.get(wire.getSource()
					.getTransitionId());
			AbstractTransitionPlugin target = pluginMap.get(wire.getTarget()
					.getTransitionId());

			Place p = new Place("place-" + id);
			p.setName("Place " + p.getId());

			// Checking for inspector
			if (wire.getSource().isInspectioned()
					&& wire.getSource().getInspector().isInspectorBlockSet()) {
				IGraphicBlock inspector = wire.getSource().getInspector()
						.getInspectorBlock();

				// Creating the inspector transition
				AbstractTransitionPlugin inspectorPlugin = BundleToolBox
						.getDefault().getPlugin(inspector.getBundleSymbolicName(),
								inspector.getTransitionId(),
								inspector.getTransitionName());
				inspectorPlugin
						.setCategory(IPluginsFrameworkConstants.INSPECTOR);

				// Adding the transition inspector to the net
				net.addInspector(inspectorPlugin);

				// Connecting the inspector to the place
				p.addInspector(inspectorPlugin);
			}

			// wiring
			InputArc ia = new InputArc("ia-" + id, p, target);
			ia.setExpression(new ArcExpression());
			ia.setOrder(wire.getTargetOrder());
			ia.setName("Input Arc " + ia.getId());
			OutputArc oa = new OutputArc("oa-" + id, source, p);
			oa.setName("Output Arc " + oa.getId());

			// Adding elements to the Net
			net.add(ia);
			net.add(p);
			net.add(oa);

			/*
			 * Looking for start and end transitions (generators and
			 * serializers) to create start and end places
			 */

			if (source.getCategory() == IPluginsFrameworkConstants.GENERATOR) {
				Place startPlace = new Place("start-place-"
						+ wire.getSource().getTransitionId());
				startPlace.setName("Start Place " + startPlace.getId());
				// wiring
				InputArc iaStartPlace = new InputArc("ia-start-place-"
						+ wire.getSource().getTransitionId(), startPlace,
						source);
				iaStartPlace.setExpression(new ArcExpression());
				iaStartPlace.setName("Input Arc " + iaStartPlace.getId());
				// Adding elements to the Net
				net.add(startPlace);
				net.add(iaStartPlace);
			}

			if (target.getCategory() == IPluginsFrameworkConstants.SERIALIZER) {
				Place endPlace = new Place("end-place-"
						+ wire.getTarget().getTransitionId());
				endPlace.setName("End Place " + endPlace.getId());
				// wiring
				OutputArc oaEndPlace = new OutputArc("oa-end-place-"
						+ wire.getTarget().getTransitionId(), target, endPlace);
				oaEndPlace.setName("Output Arc " + oaEndPlace.getId());
				// Adding elementes to the Net
				net.add(endPlace);
				net.add(oaEndPlace);
			}
		}

		return net;
	}

	public GraphicNet getGraphicNetFromNet(String gnetId, Net net, String netFile, IGraphicInformationsHandler handler) {
		GraphicNet gnet = new GraphicNet(gnetId, net.getName(), netFile, "Not saved yet");
		gnet.enableValidation(false);
		//Creating GraphicNet Blocks and wiring. No block positions informations
		//are computed in this alghoritm, this is demanded to the IGraphicInformationHandler
		//Alghoritm:
		
		//Creating blocks
		for(ITransition trans:net.getAllTransitions()){
			AbstractTransitionPlugin plugin = (AbstractTransitionPlugin)trans;
			IGraphicBlock block = null;
			String category = plugin.getCategory();
			Bundle bundle = null;
			try {
				bundle = BundleToolBox.getDefault().getBundle(plugin.getPluginId());
			} catch (BundleNotFoundException e) {
				logger.log(this, e.getMessage());
			}
			if(category.equals(IPluginsFrameworkConstants.GENERATOR)){
				block = new GGenerator(bundle, gnet);
			} else if(category.equals(IPluginsFrameworkConstants.FILTER)){
				block = new GFilter(bundle, gnet);
			} else if(category.equals(IPluginsFrameworkConstants.TRANSFORMER)){
				block = new GTransformer(bundle, gnet);
			} else if(category.equals(IPluginsFrameworkConstants.SERIALIZER)){
				block = new GSerializer(bundle, gnet);
			} else if(category.equals(IPluginsFrameworkConstants.SPLIT)){
				block = new GSplit(bundle, gnet);
			} else if(category.equals(IPluginsFrameworkConstants.JOIN)){
				block = new GJoin(bundle, gnet);
			}
			
			if(!category.equals(IPluginsFrameworkConstants.INSPECTOR)){
				block.setTransitionId(plugin.getAbstractTransitionId());
				block.setTransitionName(plugin.getAbstractTransitionName());
				gnet.addBlock(block);	
			}
		}
		
		//Creating wiring
		for(Place place : net.getAllPlaces()){
			if(!place.isStartPlace() && !place.isEndPlace()){
				GraphicWire wire;
				for(OutputArc oa : place.getInputArcs()){
					for(InputArc ia : place.getOutputArcs()){
						AbstractTransitionPlugin sourcePlugin = (AbstractTransitionPlugin)oa.getTransition();
						AbstractTransitionPlugin targetPlugin = (AbstractTransitionPlugin)ia.getTransition();
						IGraphicBlock source = gnet.getBlock(sourcePlugin.getAbstractTransitionId());
						IGraphicBlock target = gnet.getBlock(targetPlugin.getAbstractTransitionId());
						int sourceOrder = sourcePlugin.getOutputArcs().indexOf(oa);
						int targetOrder = ia.getOrder();
						
						wire = new GraphicWire(source, sourceOrder, target, targetOrder);
						gnet.addWire(wire);
					}
				}
			}
			for(ITransition trans : place.getInspectors()){
				AbstractTransitionPlugin insPlugin = (AbstractTransitionPlugin)trans;
				for(OutputArc oa : place.getInputArcs()){
					AbstractTransitionPlugin plugin = (AbstractTransitionPlugin)oa.getTransition();
					IGraphicBlock block = gnet.getBlock(plugin.getAbstractTransitionId());
					block.addInspector();

					Bundle bundle = null;
					try {
						bundle = BundleToolBox.getDefault().getBundle(insPlugin.getPluginId());
					} catch (BundleNotFoundException e) {
						logger.log(this, e.getMessage());
					}
					//Assume inspector as Serializer but it will work
					//fine as well if it would be a different transition
					IGraphicBlock inspector = new GSerializer(bundle,gnet);
					inspector.setTransitionId(insPlugin.getAbstractTransitionId());
					inspector.setTransitionName(insPlugin.getAbstractTransitionName());
					block.getInspector().addInspectorBlock(inspector);
				}
			}
		}
		
		handler.computeBlocksPosition(gnet);
		
		gnet.enableValidation(true);
		return gnet;
	}

	public GraphicNet getGraphicNetFromTask(String gnetId, Task task, IGraphicInformationsHandler handler) {
		GraphicNet gnet;
		Net net = task.getNet();
		String netFile = task.getNetFile();
		
		gnet = getGraphicNetFromNet(gnetId, net, netFile, handler);
		
		gnet.enableValidation(false);
		
		gnet.setParamFileName(task.getParamFile());
		
		//Analyze Task parameters to add in the GraphicNet
		//Alghoritm:
		Parameters params = task.getParam();
		for(String transitionId : params.getPluginIdList()){
			IGraphicBlock block = gnet.getBlock(transitionId);
			for(String paramName : params.getParameterNames(transitionId)){
				String value = (String)params.getParameterValue(transitionId, paramName);
				block.setParameter(paramName, value);
			}
		}
		
		gnet.enableValidation(true);
		
		return gnet;
	}

	public String getUniqueTransitionId(GraphicNet gnet) {
		String id = null;
		int count = 0;

		boolean isUnique = false;
		while (!isUnique) {
			id = "tr" + count++;

			isUnique = isUniqueTransitionId(gnet, id);
		}

		return id;
	}

	public boolean isUniqueTransitionId(GraphicNet gnet, String id) {
		boolean isUnique = true;

		for (int i = 0; isUnique && i < gnet.getBlockList().size(); i++) {
			IGraphicBlock block = gnet.getBlockList().get(i);
			if (block.getTransitionId().equals(id)) {
				isUnique = false;
			}
		}
		//Check for unique id also in the inspector list
		for (int i=0;isUnique && i<gnet.getInspectorList().size(); i++) {
			GInspector insp = gnet.getInspectorList().get(i);
			if(insp.isInspectorBlockSet()){
				if(insp.getInspectorBlock().getTransitionId().equals(id)){
					isUnique = false;
				}
			}
		}

		return isUnique;
	}

	public Token getParametersToken(GraphicNet gnet) {
		Token token = new Token();
		List<AbstractDataType> list = new ArrayList<AbstractDataType>();
		Parameters param = new Parameters();

		for (IGraphicBlock block : gnet.getBlockList()) {
			collectParameters(param, block);

			// Checking for inspector
			if (block.isInspectioned()
					&& block.getInspector().isInspectorBlockSet()) {
				IGraphicBlock inspector = block.getInspector()
						.getInspectorBlock();
				collectParameters(param, inspector);
			}
		}
		list.add(param);

		token.setValueList(list);

		return token;
	}

	private void collectParameters(Parameters param, IGraphicBlock block) {
		String transitionId = block.getTransitionId();
		Enumeration enumKeys = block.getParameters().keys();
		while (enumKeys.hasMoreElements()) {
			String paramName = (String) enumKeys.nextElement();
			String paramValue = block.getParameter(paramName);
			if(paramValue!=null)
				param.setParameterValue(transitionId, paramName, paramValue);
		}
	}

	public GraphicNetAsTaskValidationStatus validateGraphicNetAsTask(GraphicNet gnet) {
		GraphicNetAsTaskValidationStatus status = new GraphicNetAsTaskValidationStatus("No errors found",
				GraphicNetAsTaskValidationStatus.VALID);
		status.setNetValidation(true);
		
		if(gnet.getBlockList().size()==0){
			status = new GraphicNetAsTaskValidationStatus("Empty net",
					GraphicNetAsTaskValidationStatus.WARNING);
			status.setNetValidation(false);
		} else {
			Task task = getTaskFromGraphicNet(gnet);

			try {
				validateTask(task);
			} catch (CoreException e) {
				status = new GraphicNetAsTaskValidationStatus(e);
				status.setNetValidation(task.getNet().isValid());
			}			
		}

		return status;
	}

	/**
	 * This method set the Net validation flag to true or false depending on
	 * validation results. Eventually, It throws a CoreException if some
	 * validation errors are found.
	 * @param net The Net object to validate
	 * @throws CoreException Throwed in case of validation errors
	 */
	private void validateNet(Net net) throws CoreException{
		logger.log(this, "Starting Net validation process");
		NetValidationService netValidator;

		netValidator = (NetValidationService) BundleToolBox.getDefault()
				.getService("org.jdpf.core.netvalidator.NetValidator");

		// Static validation
		logger.log(this, "Starting Net static validation process");

		net.setValid(true); // TODO: Bug fix: NetValidation doesn't set the
		// validation flag to true if the net is valid

		netValidator.validateNetStatically(net);
		
		logger.log(this, "Net static validation process terminated");

		if (net.isValid()) {
			// Dinamic validation
			logger.log(this, "Starting Net dinamic validation process");

			netValidator.validateNetDynamically(new NetValidatorExecutor(net));

			logger.log(this, "Net dinamic validation process terminated");
		}

		logger.log(this, "Net validation process terminated");
	}
	
	/**
	 * This method set the Task validation flag to true or false depending on
	 * validation results. Eventually, It throws a CoreException if some
	 * validation errors are found.
	 * @param task The Task object to validate
	 * @throws CoreException Throwed in case of validation errors
	 */
	private void validateTask(Task task) throws CoreException{
		logger.log(this, "Starting Task validation process");

		Net net = task.getNet();
	
		//Net Validation
		validateNet(net);
	
		task.setValidity(net.isValid());

		if (task.isValid()) {
			// Check parametrization
			logger.log(this, "Starting parameters validation process");

			try {
				task.checkParameters();
			} catch (CoreException e) {
				task.setValidity(false);

				logger.error(this, e.getMessage());
				
				throw e;
			}

			logger.log(this, "Parameters validation process terminated");
		}

		logger.log(this, "Task validation process terminated");
	}

	public Task getTaskFromGraphicNet(GraphicNet gnet) {
		Token token = getParametersToken(gnet);
		Parameters params = (Parameters) token.getValueList().get(0);
		
		Net net = getNetFromGraphicNet(gnet);
		
		Task task = new Task();
		task.setNetFile(gnet.getNetName() + " On Fly");
		task.setParamFile(gnet.getNetName() + " Parameters On Fly");
		task.setNetName(net.getName());
		task.setNet(net);
		task.setParam(params);
		task.setValidity(false);

		return task;
	}
}